use serde::{Deserialize, Serialize};
use thiserror::Error;
use tracing::trace;

use crate::{
    BlockChainHash, BlockHeight, BlockId,
    constants::{GENESIS_BLOCK_ID, PROOF_OF_WORK_LIMIT_NBITS},
    transaction::TransactionError,
};

mod block_impl;
mod target_threshold;

pub use block_impl::{Block, BlockHeader, BlockInDb};

#[derive(Error, Debug, PartialEq)]
pub enum BlockError {
    #[error("No transaction")]
    NoTransaction,

    // NoPrevBlock
    #[error("No prev block")]
    NoPrevBlock,

    // block size is too large
    #[error("Block size is too large")]
    BlockSizeTooLarge,

    // deserialize error
    #[error("Deserialize error")]
    DeserializeError,

    #[error("Merkle root hash error")]
    MerkleRootHashError,

    // read from db error
    #[error("Read from db error")]
    ReadFromDbError(String),

    // FirstTransactionNotCoinbase
    #[error("First transaction not coinbase")]
    FirstTransactionNotCoinbase,

    // CoinbaseTransactionInMiddle
    #[error("Coinbase transaction in middle")]
    CoinbaseTransactionInMiddle,

    // from TransactionError
    #[error("Transaction error: {0}")]
    TransactionError(#[from] TransactionError),

    // nBits below minimum work
    #[error("NBits below minimum work(nbits: {0:#x})")]
    NBitsBelowMinimumWork(u32),

    // hash doesn't match nBits
    #[error("Hash doesn't match nBits")]
    HashDoesNotMatchNBits,

    // hashMerkleRoot mismatch
    #[error("HashMerkleRoot mismatch")]
    HashMerkleRootMismatch,

    // the exponent in nbits is too large
    #[error("Exponent in nBits is too large")]
    ExponentInNBitsIsTooLarge,
}

pub use target_threshold::{nbits_to_target_threshold, target_threshold_to_nbits};

pub fn nbits_to_hash(nbits: u32) -> BlockChainHash {
    let target = nbits_to_target_threshold(nbits).unwrap();

    // Convert the target to bytes and ensure it is 32 bytes long
    let mut target_bytes = target.to_bytes_be();
    let len = target_bytes.len();
    assert!(len <= 32);

    if len < 32 {
        let mut padding = vec![0u8; 32 - len];
        padding.extend(target_bytes);
        target_bytes = padding;
    }

    let target_bytes: [u8; 32] = target_bytes.try_into().expect("BigUint to [u8; 32] failed");

    BlockChainHash::new(target_bytes)
}

pub fn create_block_header_for_mining(prev_block_hash: &BlockChainHash, nbits: u32) -> BlockHeader {
    let version: u32 = crate::constants::BLOCK_CHIAN_VERSION;
    let merkle_root_hash: BlockChainHash = BlockChainHash::new(crate::constants::NULL_BLOCK_ID);
    let sys_time_now = bc_util::get_current_sys_time_seconds();
    trace!(
        "sys_time_now: {}, prev_block_hash:{}",
        sys_time_now, prev_block_hash
    );
    let timestamp: u32 = sys_time_now as u32;
    let nonce: u32 = 0;

    BlockHeader::new(
        version,
        prev_block_hash,
        merkle_root_hash,
        timestamp,
        nbits,
        nonce,
    )
}

pub fn create_block_header_for_genesis_block() -> BlockHeader {
    let version: u32 = crate::constants::BLOCK_CHIAN_VERSION;
    let prev_block_hash: BlockChainHash = BlockChainHash::new([0; 32]);
    let merkle_root_hash: BlockChainHash = BlockChainHash::new([0; 32]);
    let timestamp: u32 = 1735660800; // 2025-1-1 00:00:00 Asia/Shanghai UTC in Unix timestamp
    let nbits: u32 = PROOF_OF_WORK_LIMIT_NBITS;
    let nonce: u32 = 0;

    BlockHeader::new(
        version,
        &prev_block_hash,
        merkle_root_hash,
        timestamp,
        nbits,
        nonce,
    )
}

pub fn create_block_header_for_test(nbits: u32) -> BlockHeader {
    let prev_block_hash: BlockChainHash = BlockId::from_hex_str(GENESIS_BLOCK_ID).unwrap();
    create_block_header_for_mining(&prev_block_hash, nbits)
}

#[derive(Clone, Serialize, Deserialize, PartialEq)]
pub struct BlockIndexNode {
    blk_id: BlockId,
    prev_blk_id: BlockId,
    next_blk_id: Option<BlockId>,

    height: BlockHeight,

    version: u32,
    merkle_root_hash: BlockChainHash,
    timestamp: u32,
    nbits: u32,
    nonce: u32,
}

impl std::fmt::Debug for BlockIndexNode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "BlockIndexNode {{ blk_id: {}, prev_blk_id: {}, next_blk_id: {:?}, height: {}, version: {}, merkle_root_hash: {}, timestamp: {}, nbits: {}, nonce: {} }}",
            self.blk_id,
            self.prev_blk_id,
            self.next_blk_id,
            self.height,
            self.version,
            self.merkle_root_hash,
            self.timestamp,
            self.nbits,
            self.nonce,
        )
    }
}

impl std::fmt::Display for BlockIndexNode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}", self)
    }
}

impl BlockIndexNode {
    pub fn new_by_block(block: &Block) -> Self {
        Self {
            blk_id: block.calc_block_id(),
            prev_blk_id: *block.get_prev_block_id(), // mark <--
            next_blk_id: None,

            height: 0,

            version: block.get_version(),
            merkle_root_hash: block.get_merkle_root_hash(),
            timestamp: block.get_timestamp(),
            nbits: block.get_nbits(),
            nonce: block.get_nonce(),
        }
    }

    pub fn new_by_block_in_db(blk_in_db: &BlockInDb) -> Self {
        Self {
            blk_id: blk_in_db.calc_block_id(),
            prev_blk_id: *blk_in_db.get_prev_block_id(),
            next_blk_id: None,

            height: 0,

            version: blk_in_db.get_version(),
            merkle_root_hash: *blk_in_db.get_merkle_root_hash(),
            timestamp: blk_in_db.get_timestamp(),
            nbits: blk_in_db.get_nbits(),
            nonce: blk_in_db.get_nonce(),
        }
    }

    pub fn get_block_id(&self) -> &BlockId {
        &self.blk_id
    }

    pub fn set_next_block_id(&mut self, blk_id: &BlockId) {
        self.next_blk_id = Some(*blk_id);
    }

    pub fn reset_next_block_id(&mut self) {
        self.next_blk_id = None;
    }

    pub fn get_next_block_id(&self) -> Option<&BlockId> {
        if self.next_blk_id.is_none() {
            None
        } else {
            Some(self.next_blk_id.as_ref().unwrap())
        }
    }

    pub fn get_prev_block_id(&self) -> &BlockId {
        &self.prev_blk_id
    }

    pub fn set_height(&mut self, height: BlockHeight) {
        self.height = height;
    }

    pub fn get_height(&self) -> BlockHeight {
        self.height
    }

    pub fn get_timestamp(&self) -> u32 {
        self.timestamp
    }

    pub fn get_nbits(&self) -> u32 {
        self.nbits
    }
}

pub type BlockIndexCache = std::collections::HashMap<BlockId, BlockIndexNode>;

#[cfg(test)]
mod tests_for_learning {
    use super::*;
    use crate::constants;

    // macro_rules! offset_of {
    //     ($Struct:ty, $field:ident) => {{
    //         let base = std::ptr::null::<$Struct>();
    //         let field_ptr = unsafe { std::ptr::addr_of!((*base).$field) as usize };
    //         field_ptr - (base as usize)
    //     }};
    // }

    /// ### 一、默认内存布局（`#[repr(Rust)]`）
    /// 当不显式指定内存布局时，Rust 编译器会**自动优化字段顺序**以最小化内存占用。规则如下：
    ///
    /// 1. **字段重排优化**
    ///    - 编译器会重新排列字段顺序，使得对齐填充（Padding）最少。
    ///    - 例如：一个包含 `u8`、`u64`、`u8` 字段的结构体，可能被重排为 `u64` 在前，两个 `u8` 在后，从而减少填充。
    ///
    /// 2. **对齐（Alignment）**
    ///    - 结构体的对齐值是其所有字段中最大的对齐值。
    ///    - 每个字段的地址必须是其类型对齐值的整数倍。
    ///
    /// 3. **填充字节（Padding）**
    ///    - 编译器在字段之间插入填充字节以满足对齐要求。

    #[test]
    fn test_block_index_size() {
        println!(
            "Size of BlockIndex: {}",
            std::mem::size_of::<BlockIndexNode>()
        );
        println!(
            "Align of BlockIndex: {}",
            std::mem::align_of::<BlockIndexNode>()
        );

        println!(
            "(Offset, size) of prev_hash: ({}, {})",
            std::mem::offset_of!(BlockIndexNode, prev_blk_id),
            std::mem::size_of::<Option<BlockId>>()
        );
        println!(
            "(Offset, size) of next_hash: ({}, {})",
            std::mem::offset_of!(BlockIndexNode, next_blk_id),
            std::mem::size_of::<Option<BlockId>>()
        );
        println!(
            "(Offset, size) of height: ({}, {})",
            std::mem::offset_of!(BlockIndexNode, height),
            std::mem::size_of::<BlockHeight>()
        );
        println!(
            "(Offset, size) of version: ({}, {})",
            std::mem::offset_of!(BlockIndexNode, version),
            std::mem::size_of::<u32>()
        );
        println!(
            "(Offset, size) of merkle_root_hash: ({}, {})",
            std::mem::offset_of!(BlockIndexNode, merkle_root_hash),
            std::mem::size_of::<BlockChainHash>()
        );
        println!(
            "(Offset, size) of timestamp: ({}, {})",
            std::mem::offset_of!(BlockIndexNode, timestamp),
            std::mem::size_of::<u32>()
        );
        println!(
            "(Offset, size) of nbits: ({}, {})",
            std::mem::offset_of!(BlockIndexNode, nbits),
            std::mem::size_of::<u32>()
        );
        println!(
            "(Offset, size) of nonce: ({}, {})",
            std::mem::offset_of!(BlockIndexNode, nonce),
            std::mem::size_of::<u32>()
        );

        assert_eq!(
            std::mem::size_of::<BlockId>(),
            constants::HASH256_BYTES_LENGTH
        );
        assert_eq!(
            std::mem::size_of::<Option<BlockId>>(),
            constants::HASH256_BYTES_LENGTH + 1
        );

        let align = std::mem::align_of::<BlockIndexNode>();
        assert_eq!(align, std::mem::size_of::<BlockHeight>());
        let size: usize = 2 * std::mem::size_of::<BlockId>()
            + std::mem::size_of::<Option<BlockId>>()
            + std::mem::size_of::<BlockHeight>()
            + 4 * std::mem::size_of::<u32>()
            + std::mem::size_of::<BlockId>();

        let aligned_size = (size + align - 1) & !(align - 1);

        assert_eq!(std::mem::size_of::<BlockIndexNode>(), aligned_size);
    }

    #[test]
    fn test_map_or_else() {
        let mut maybe_none: Option<i32> = None;
        assert_eq!(maybe_none.map_or_else(|| 0, |x| x), 0);

        maybe_none = Some(1);
        assert_eq!(maybe_none.map_or_else(|| 0, |x| x), 1);
    }
}
